package com.aps.common.code;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * 分布式雪花算法ID生成器（修复重复ID问题）
 * 结构：37位时间戳 + 10位机器号 + 17位序列号 → 唯一64位ID
 */
@Service
public class DistributedSnowflakeIdGenerator {

    private static final Object LOCK = new Object();

    /** 自定义纪元（2024-01-01 00:00:00） */
    private static final long EPOCH = LocalDateTime.of(2024, 1, 1, 0, 0, 0)
            .atZone(ZoneId.systemDefault())
            .toInstant()
            .toEpochMilli();

    /** 时间戳位数（37位，支持43年） */
    private static final int TIMESTAMP_BITS = 37;
    /** 机器号位数（10位，支持1-999） */
    public static final int MACHINE_ID_BITS = 10;
    /** 序列号位数（17位，单毫秒最大131072个ID） */
    private static final int SEQUENCE_BITS = 17;

    /** 时间戳掩码（37位全1，防止溢出） */
    private static final long TIMESTAMP_MASK = (1L << TIMESTAMP_BITS) - 1;
    /** 机器号掩码（10位全1，支持0-1023） */
    public static final long MACHINE_ID_MASK = (1L << MACHINE_ID_BITS) - 1;
    /** 序列号掩码（17位全1） */
    private static final long SEQUENCE_MASK = (1L << SEQUENCE_BITS) - 1;

    /** 时间戳左移位数（机器号+序列号=27位） */
    private static final int TIMESTAMP_SHIFT = MACHINE_ID_BITS + SEQUENCE_BITS;
    /** 机器号左移位数（序列号=17位） */
    private static final int MACHINE_ID_SHIFT = SEQUENCE_BITS;

    // ============================== 类级别状态 ==============================
    /** 最后生成ID的时间戳（毫秒） */
    private static long lastTimestamp = -1L;
    /** 当前序列号（同一毫秒内自增） */
    private static long sequence = 0L;
    /** 机器号（从环境变量获取，1-999） */
    public static long MACHINE_ID;

    /**
     * 初始化机器号（从环境变量读取，必须手动调用！）
     */
    public static void init() {
        String machineIdStr = System.getenv("SNOWFLAKE_MACHINE_ID");
        MACHINE_ID = machineIdStr != null ? Long.parseLong(machineIdStr) : 1L;

        // 校验机器号范围（1-999）
        if (MACHINE_ID < 1 || MACHINE_ID > 999) {
            throw new IllegalArgumentException("机器号必须为1-999的整数（环境变量：SNOWFLAKE_MACHINE_ID）");
        }
        // 确保机器号不超过掩码范围（0-1023）
        if ((MACHINE_ID & MACHINE_ID_MASK) != MACHINE_ID) {
            throw new IllegalArgumentException("机器号超出10位二进制范围（最大1023）");
        }
    }

    /**
     * 生成下一个雪花ID（long类型，64位）
     * 核心：时间戳左移27位 | 机器号左移17位 | 序列号（无重叠）
     */
    public static synchronized long nextId() {
        synchronized (LOCK) {
            long currentTime = System.currentTimeMillis();

            // 1. 处理时钟回拨
            if (currentTime < lastTimestamp) {
                long offsetMs = lastTimestamp - currentTime;
                handleClockBackward(offsetMs);
            }

            // 2. 同一毫秒内：序列号自增，溢出则等待下一毫秒
            if (currentTime == lastTimestamp) {
                sequence = (sequence + 1) & SEQUENCE_MASK;
                if (sequence == 0) {
                    currentTime = waitNextMillis(currentTime);
                    lastTimestamp = currentTime;
                    sequence = 0L;
                }
            } else {
                sequence = 0L;
                lastTimestamp = currentTime;
            }

            // 3. 组合ID：时间戳左移27位 | 机器号左移17位 | 序列号（无重叠）
            long timestampPart = ((currentTime - EPOCH) & TIMESTAMP_MASK) << TIMESTAMP_SHIFT;
            long machineIdPart = (MACHINE_ID & MACHINE_ID_MASK) << MACHINE_ID_SHIFT;
            long sequencePart = sequence & SEQUENCE_MASK;

            return timestampPart | machineIdPart | sequencePart;
        }
    }

    /**
     * 生成20位十进制ID字符串（因为数据库id如果用这个的话 为 bigint ，需要末尾补0）
     */
    public static String generate20DigitId() {
        long id = nextId();
        return StringUtils.rightPad(String.valueOf(id), 20, '0');
    }

    /**
     * 处理时钟回拨
     */
    private static void handleClockBackward(long offsetMs) {
        if (offsetMs <= 5) {
            try {
                Thread.sleep(offsetMs);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("时钟回拨恢复被中断");
            }
        } else {
            throw new RuntimeException("时钟回拨" + offsetMs + "毫秒，拒绝生成ID");
        }
    }

    /**
     * 等待到下一毫秒
     */
    private static long waitNextMillis(long currentTime) {
        long newTime;
        do {
            newTime = System.currentTimeMillis();
        } while (newTime <= currentTime);
        return newTime;
    }
}